package cn.miao.service.impl;

import cn.hutool.core.text.CharSequenceUtil;
import cn.miao.config.HighlightResultMapper;
import cn.miao.doc.CourseDoc;
import cn.miao.dto.SearchCourseDto;
import cn.miao.vo.AggrTermsVo;
import cn.miao.repository.CourseESRepository;
import cn.miao.service.ICourseESService;
import cn.miao.util.AjaxResult;
import cn.miao.util.PageList;
import cn.miao.vo.CourseAjaxResult;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @program: springcloud-hrm
 * @description:
 * @author: MiaoWei
 * @create: 2021-11-27 21:06
 **/
@Service
public class CourseESServiceImpl implements ICourseESService {

    @Autowired
    private CourseESRepository courseESRepository;
    @Autowired
    private HighlightResultMapper highlightResultMapper;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 保存或更新
     *
     * @param courseDoc 当然医生
     * @return {@link AjaxResult}
     */
    @Override
    public AjaxResult saveOrUpdate(CourseDoc courseDoc) {
        courseESRepository.save(courseDoc);
        return AjaxResult.me();
    }

    /**
     * 删除
     *
     * @param id id
     * @return {@link AjaxResult}
     */
    @Override
    public AjaxResult del(Long id) {
        courseESRepository.deleteById(id);
        return AjaxResult.me();
    }

    /**
     * 搜索
     *
     * @param id id
     * @return {@link AjaxResult}
     */
    @Override
    public AjaxResult search(Long id) {
        Optional<CourseDoc> courseDoc = courseESRepository.findById(id);
        return AjaxResult.me().setResultObj(courseDoc.get());
    }

    /**
     * 搜索课程
     *
     * @param dto dto
     * @return {@link AjaxResult}
     */
    @Override
    public AjaxResult searchCourse(SearchCourseDto dto) {
        //创建查询构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //构建组合查询
        this.getBoolQueryBuilder(dto,queryBuilder);

        //其他字段查询-如分页、排序
        this.otherFiledQuery(dto, queryBuilder);

        //高亮
        HighlightBuilder.Field hight = new HighlightBuilder.Field("name").preTags("<font style='color:red'><b>").postTags("</b></font>");
        queryBuilder.withHighlightFields(hight);

        //分组统计数量
        queryBuilder.addAggregation(AggregationBuilders.terms("tenantNameAgg").field("tenantName").order(Terms.Order.count(false)).size(20));
        queryBuilder.addAggregation(AggregationBuilders.terms("gradeNameAgg").field("gradeName").order(Terms.Order.count(false)).size(20));


        //搜索结果
        //Page<CourseDoc> courseDocs = courseESRepository.search(queryBuilder.build());
        AggregatedPage<CourseDoc> courseDocs = elasticsearchTemplate.queryForPage(queryBuilder.build(), CourseDoc.class, highlightResultMapper);

        //聚合
        StringTerms tenantNameAggTerms = (StringTerms) courseDocs.getAggregation("tenantNameAgg");
        StringTerms gradeNameAggTerms = (StringTerms) courseDocs.getAggregation("gradeNameAgg");

        //把机构名字聚合，搜集成字符串里集合
        List<AggrTermsVo> tenantNames = tenantNameAggTerms.getBuckets().stream().map(
                bucket ->  new AggrTermsVo(bucket.getKeyAsString(),bucket.getDocCount())
        ).collect(Collectors.toList());
        //把等级名字聚合，搜集成字符串里集合
        List<AggrTermsVo> gradeNameAgg = gradeNameAggTerms.getBuckets().stream().map(
                bucket ->  new AggrTermsVo(bucket.getKeyAsString(),bucket.getDocCount())
        ).collect(Collectors.toList());

        //聚合结果处理-另一种解决办法!
//        Map<String, Aggregation> aggregationMap = page.getAggregations().asMap();
//        System.out.println(aggregationMap);
//        aggregationMap.keySet().forEach(key->{
//            //创建聚合列表
//            List<TermBucket> termBuckets = new ArrayList<>();
//            // 通过key获取查询的聚合
//            StringTerms aggregation = (StringTerms)aggregationMap.get(key);
//            // 获取里面的bucket对象
//            aggregation.getBuckets().forEach(bucket -> {
//                // 获取名字
//                String name = bucket.getKey().toString();
//                // 获取值
//                long docCount = bucket.getDocCount();
//                // 往列表中添加新的buckets
//                termBuckets.add(new TermBucket(name, docCount));
//            });
//            // 将值返回到结果中
//            aggrResult.put(key, termBuckets);
//        });

        return CourseAjaxResult.me().setGradeAggs(gradeNameAgg).setTenantAggs(tenantNames).setResultObj(new PageList<>(courseDocs.getTotalElements(), courseDocs.getContent()));
    }

    /**
     * 其他字段查询
     *
     * @param dto          dto
     * @param queryBuilder 查询构建器
     */
    private void otherFiledQuery(SearchCourseDto dto, NativeSearchQueryBuilder queryBuilder) {
        //分页(这里因为Pageable接口的子类PageRequest默认构造已被弃用所以采用of)
        queryBuilder.withPageable(PageRequest.of(dto.getPage() - 1, dto.getRows()));

        //排序
        if (CharSequenceUtil.isNotBlank(dto.getSortField())) {
            String sortField;
            switch (dto.getSortField()) {
                //默认按照人气排行进行排序
                case "xl" :
                    sortField = "saleCount";
                    break;
                case "xp":
                    sortField = "onlineTime";
                    break;
                case "pl":
                    sortField = "commentCount";
                    break;
                case "jg":
                    sortField = "price";
                    break;
                case "rq":
                    sortField = "viewCount";
                    break;
                default:
                    sortField = "saleCount";
            }

            SortOrder sortOrder = SortOrder.DESC;
            //判断是否为空或者等于desc
            if (CharSequenceUtil.isBlank(dto.getSortType()) || dto.getSortType().equalsIgnoreCase(SortOrder.ASC.toString())) {
                sortOrder = SortOrder.ASC;
            }
            queryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortOrder));
        }
    }

    /**
     * 获取组合查询构建器
     *
     * @param dto dto
     * @return {@link BoolQueryBuilder}
     */
    private void getBoolQueryBuilder(SearchCourseDto dto,NativeSearchQueryBuilder queryBuilder) {
        //关键字-课程名
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (CharSequenceUtil.isNotEmpty(dto.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("name", dto.getKeyword()));
        }
        //机构名字
        if (CharSequenceUtil.isNotEmpty(dto.getTenantName())) {
            boolQuery.filter(QueryBuilders.termQuery("tenantName", dto.getTenantName()));
        }
        //等级名字
        if (CharSequenceUtil.isNotEmpty(dto.getGradeName())) {
            boolQuery.filter(QueryBuilders.termQuery("gradeName", dto.getGradeName()));
        }
        //最低价格 >=
        if (CharSequenceUtil.isNotEmpty(dto.getPriceMin())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(dto.getPriceMin()));
        }
        //最高价格 <=
        if (CharSequenceUtil.isNotEmpty(dto.getPriceMax())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").lte(dto.getPriceMax()));
        }
        //课程类型id
        if (Objects.nonNull(dto.getCourseTypeId())) {
            boolQuery.filter(QueryBuilders.termQuery("courseTypeId", dto.getCourseTypeId()));
        }
        queryBuilder.withQuery(boolQuery);
    }
}
